#include "thread.h"
#include "processor.h"
#include "uarch/settings.h"
#include "uarch/sim.h"
#include "uarch/types.h"

#if (SESC_EMUL)
#include "ThreadContext.h"
#include "InstDesc.h"
#endif

extern Simulator Sim;

using namespace std;

#if (!SESC_EMUL)
Thread::Thread (int tid)
{
    this->tid = tid;
    this->tgid = -1;
    this->pid = -1;
    this->pgid = -1;
    this->ppid = -1;

    this->cpu = -1;
    this->sched_time = Global_Clock;
    this->state = THREAD_UNDEF;
    this->context = NULL;
    Sim.emul.thread_map[this->tid] = this;
}
#else
Thread::Thread (ThreadContext::pointer context)
{
    this->tid = context->gettid ();
    this->tgid = context->gettgid ();
    this->pid = context->getPid ();
    this->pgid = context->getpgid ();
    this->ppid = context->getppid ();
    this->cpu = -1;
    this->sched_time = Global_Clock;
    this->state = THREAD_UNDEF;
    this->context = context;
    Sim.emul.thread_map[this->tid] = this;
}
#endif

Thread::~Thread ()
{
}

#if (!SESC_EMUL)
bool Thread::is_suspended ()
{
    return false;
}
#else
bool Thread::is_suspended ()
{
    return context->isSuspended ();
}
#endif

#if (!SESC_EMUL)
bool Thread::is_exited ()
{
    return trace_queue[tid].empty ();
}
#else
bool Thread::is_exited ()
{ 
    return context->isExited ();
}
#endif

#if (!SESC_EMUL)
Insn *Thread::fetch ()
{
    Insn *insn;

    if (!trace_queue[tid].empty ()) 
    {
        insn = trace_queue[tid].back ();
        trace_queue[tid].pop_back ();

        if (trace_queue[tid].empty ())
            this->state = THREAD_EXITED;

        return insn;
    }
    else
    {
        this->state = THREAD_EXITED;
        return new Insn (OpNop);
    }
}
Insn *Thread::peek ()
{
    return NULL;
}
#else // SESC_EMUL
Insn *Thread::fetch ()
{
    paddr_t pc;
    paddr_t addr;
    Insn *insn;

    InstDesc *desc;  // libemul/InstDesc.h

    assert (ThreadContext::pid2context.size () <= (unsigned)Sim.settings.num_nodes);

    assert (this->context);
    assert (!this->context->isSuspended());
    assert (!this->context->isExited());

    pc = this->context->getIAddr ();
    desc = this->context->emulInst ();
    
    if (desc == NULL)
        return new Insn (OpNop);

    /** Sesc specific  */
    switch (desc->typ & TypOpMask)
    {
    case TypNop:
        return new Insn (OpNop);

    case TypIntOp:
        switch (desc->typ & TypSubMask)
        {
        case IntOpALU: return new Insn (OpIntALU);
        case IntOpMul: return new Insn (OpIntMul);
        case IntOpDiv: return new Insn (OpIntDiv);
        }

    case TypFpOp:
        switch (desc->typ & TypSubMask)
        {
        case FpOpALU: return new Insn (OpFpALU);
        case FpOpMul: return new Insn (OpFpMul);
        case FpOpDiv: return new Insn (OpFpDiv);
        }

    case TypBrOp:
        switch (desc->typ & TypSubMask)
        {
        case BrOpJump: return new Insn (OpBrJump);
        case BrOpCond: return new Insn (OpBrCond);
        case BrOpCall: return new Insn (OpBrCall);
        case BrOpCCall: return new Insn (OpBrCCall);
        case BrOpRet: return new Insn (OpBrRet);
        case BrOpCRet: return new Insn (OpBrCRet);
        case BrOpTrap: return new Insn (OpBrTrap);
        case BrOpCTrap: return new Insn (OpBrCTrap);
        }
        break;

    case TypMemOp:
        addr = context->getDAddr ();
        context->setDAddr (0);

        /** Internally, Sesc turns failing sc to nop.  */
        if (addr == 0)
            return new Insn (OpNop);
    
        switch (desc->typ & TypSubMask)
        {
        case TypMemLd:
            Sim.get_PR (cpu)->stats->load_count++;
            return new Insn (OpMemLd, addr);

        case TypMemSt:
            Sim.get_PR (cpu)->stats->store_count++;
            return new Insn (OpMemSt, addr);

        case TypSynLd:
            Sim.get_PR (cpu)->stats->load_count++;
            Sim.get_PR (cpu)->stats->ll_count++;
            if (Sim.settings.sesc_disable_llsc == false)
                return new Insn (OpSynLd, addr);
            else
                return new Insn (OpNop);

        case TypSynSt:
            Sim.get_PR (cpu)->stats->store_count++;
            Sim.get_PR (cpu)->stats->sc_count++;
            if (Sim.settings.sesc_disable_llsc == false)
                return new Insn (OpSynLd, addr);
            else
                return new Insn (OpNop);
            break;

        case TypSync:
            Sim.get_PR (cpu)->stats->sync_count++;
            return new Insn (OpSync);

        default:
            fatal_error ("Unknown opcode subtype");
            break;
        }
        break;
    default:
        fatal_error ("Unknown opcode");
    }
    fatal_error ("Unknown opcode");
}

/** Peek at an instruction without updating emulator.  Return NULL if we don't care, mreq otherwise.  */
Insn *Thread::peek ()
{
    paddr_t pc;
    paddr_t addr;
    InstTypInfo typ;  // libemul/InstDesc.h

    pc = this->context->getIAddr ();
    typ = this->context->peekInst ();

    /** Make sure the peek function didn't change the PC.  */
    if (this->context->getIAddr () != pc)
        fatal_error ("PC shouldn't change on a peek\n");

    /** Sesc specific  */
    switch (typ & TypOpMask)
    {
    case TypNop:
    case TypIntOp:
    case TypFpOp:
    case TypBrOp:
        return NULL;
        break;

    case TypMemOp:
        addr = context->getDAddr ();
        context->setDAddr (0);
        switch (typ & TypSubMask)
        {
        case TypMemLd: 
        case TypMemSt:
        case TypSync:
            return NULL;

        case TypSynLd: 
            return new Insn (OpSynLd, addr);

        case TypSynSt: 
            if (addr != 0)
                return new Insn (OpSynSt, addr);
            else
                return NULL;
        default:
            fatal_error ("Unknown opcode subtype");
            break;
        }
        break;
    default:
        fatal_error ("Unknown opcode");
    }
    fatal_error ("Unknown opcode");
}
#endif

void Thread::dump ()
{
    fprintf (stderr, "Tid: %d\n", tid);
    fprintf (stderr, "TGid: %d\n", tgid);
    fprintf (stderr, "Pid: %d\n", pid);
    fprintf (stderr, "PGid: %d\n", pgid);
    fprintf (stderr, "PPid: %d\n", ppid);
    
    switch (state) {
    case THREAD_UNDEF:
        fprintf (stderr, "State - Undefined\n");
        break;
    case THREAD_STALLED:
        fprintf (stderr, "State - Stalled\n");
        break;
    case THREAD_READY:
        fprintf (stderr, "State - Ready\n");
        break;
    case THREAD_RUNNING:
        fprintf (stderr, "State - Running\n");
        break;
    case THREAD_EXITED:
        fprintf (stderr, "State - Exited\n");
        break;
    default:
        fatal_error ("Thread State Unknown.\n");
    }
}
